package org.hk.proxy.server.web;

import cn.hutool.core.io.FileUtil;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedNioFile;
import io.netty.util.concurrent.Future;
import org.hk.proxy.common.constant.ApiStateEnum;
import org.hk.proxy.common.json.JSON;
import org.hk.proxy.server.constant.ProjectConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

public class ResponseWrapper {
    private static final Logger log = LoggerFactory.getLogger(ResponseWrapper.class);
    private static final String SERVER_VS = "LPS-0.1";
    private ChannelHandlerContext context;
    private ApiRouteResolve.ApiRoute route;

    public ResponseWrapper(ChannelHandlerContext context, ApiRouteResolve.ApiRoute route) {
        this.context = context;
        this.route = route;
    }

    public void writeAndFlush(HttpRequest request){
        if(route!=null){
            Optional.ofNullable(route.getRequestMiddleware()).ifPresent(item->item.preRequest(request));
        }
        String uri = request.uri();
        if (!ApiRouteResolve.containRoute(uri)) {
            try {
                outPage(request, context);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            Optional.ofNullable(ApiRouteResolve.getRoute(uri)).ifPresent(route -> {
                Result response = null;
                if(!route.getMethod().equalsIgnoreCase(request.method().name())){
                    response = Result.of(ApiStateEnum.METHOD_ALLOW);
                }else {
                    response = route.getRequestHandler().request(request);
                }
                outputContent(this.context, request, response);
            });
        }
    }

    protected static void outputContent(ChannelHandlerContext ctx, HttpRequest request, Result msg)  {
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.SERVER, SERVER_VS).set(HttpHeaderNames.CONTENT_TYPE,HttpHeaderValues.APPLICATION_JSON);
        if (request.protocolVersion().isKeepAliveDefault()) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        response.content().writeBytes(JSON.toJSONString(msg).getBytes(StandardCharsets.UTF_8));
        ctx.write(response);
        HttpResponseStatus status = HttpResponseStatus.valueOf(msg.getCode());
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,response.content().readableBytes());
        response.setStatus(!status.reasonPhrase().contains("Unknown")?status:HttpResponseStatus.SERVICE_UNAVAILABLE);
        ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!request.protocolVersion().isKeepAliveDefault()) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void outPage(HttpRequest request, ChannelHandlerContext context) throws IOException {
        HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
        String uri = request.uri();
        String path = ProjectConfig.config.getWebServerConfig().getWebPath() + uri;
        if (request.protocolVersion().isKeepAliveDefault()) {
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }
        if (FileUtil.exist(path)) {
            path = FileUtil.isDirectory(path) ? path + "/index.html" : path;
            if (HttpUtil.is100ContinueExpected(request)) {
                send100Continue(context);
            }
            String mimeType = MimeType.getMimeType(MimeType.parseSuffix(path));
            InputStream inputStream = new FileInputStream(path);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeType);
            response.headers().set(HttpHeaderNames.SERVER, SERVER_VS);
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, inputStream.available());
            context.write(response);
            try {
                context.write(new ChunkedNioFile(new FileInputStream(path).getChannel()));
                Future future =  context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
                addCloseListener(request,future);
            } catch (Exception e) {
                e.printStackTrace();
                context.channel().close();
            }
        } else {
            response.setStatus(HttpResponseStatus.NOT_FOUND);
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
            context.channel().write(response);
            Future future =  context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            addCloseListener(request,future);
            return;
        }
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }

    private void addCloseListener(HttpRequest request, Future future){
        if (!request.protocolVersion().isKeepAliveDefault()) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }
}
